<?php
class dbexecuter
{
    protected $dataSource;
    private $isDebug = false;

    public function __construct($dataSource) {
        $this->dataSource = $dataSource;
    }
    public function __sleep() {
        return array();
    }
    public function getDataSource() {
        return $this->dataSource;
    }
    public function getLastInsertId(){
        return $this->dataSource->connect4Write()->lastInsertId();
    }
    public function mustUseMaster($must = true) {
        $this->dataSource->mustUseMaster($must);
    }
    public function execute($sql, $bind = array()) {
        if (false == empty($bind)) {
            $this->adjust($sql, $bind);
        }       
        try {
            $conn = $this->dataSource->connect4Write();
            $timeStart = dbtime();
            if (empty($bind)) {
                $result = $conn->exec($sql);
                $timeEnd = dbtime();
                $time = $timeEnd - $timeStart;
                $this->writeDevLog($time, $sql);
            } else {
                $echoSql = $this->buildSql($sql, $bind);

                $stmt = $conn->prepare($sql);
                $result = $stmt->execute($bind);

                $timeEnd = dbtime();
                $time = $timeEnd - $timeStart;

                $this->writeDevLog($time, $echoSql);
            }
        } catch (Exception $e) {
            throw new Exception("SqlError : {$this->buildSql($sql, $bind)}, Message : ".$e->getMessage().", Trace : ".$e->getTraceAsString());
        }
        return $result;
    }
    public function query($sql, $bind = array()) {
        if (false == empty($bind)) {
            $this->adjust($sql, $bind);
        }
        try {
            $timeStart = dbtime();
            $conn = $this->dataSource->connect4Read();
            $timeStart2 = dbtime();
            if (empty($bind)) {
                $stmt = $conn->query($sql);
                $timeEnd3 = dbtime();
                $result = $stmt->fetchAll(PDO::FETCH_ASSOC);

                if ($this->isDebug)
                {
                    $stmt = $conn->query('explain '.$sql);
                    $explain = $stmt->fetchAll(PDO::FETCH_ASSOC);
                    BeanFinder::get('debug')->info(array('sql' => $sql, 'explain' => $explain));
                }

                $timeEnd4 = dbtime();
                $time = ($timeEnd4 - $timeStart) * 1000;
                $time2 = ($timeStart2 - $timeStart) * 1000;
                $time3 = 0;
                $time4 = ($timeEnd3 - $timeStart2) * 1000;
                $time5 = ($timeEnd4 - $timeEnd3) * 1000;
                
                $this->writeDevLog($time, $sql);
                if ($time > 1000) {
                    //��ʱ�� ���� 0 query fetch
                    error_log(date('m-d H:i:s')." - [$time] - [$time2] - [$time3] - [$time4] - [$time5] - [{$this->dataSource->getConnectionStatus4Read()}] - ".$sql."\n", 3, '/tmp/slowsqls.log');
                }
            } else {
                $echoSql = $this->buildSql($sql, $bind);

                $stmt = $conn->prepare($sql);
                $timeEnd2 = dbtime();
                $stmt->execute($bind);
                $timeEnd3 = dbtime();
                $result = $stmt->fetchAll(PDO::FETCH_ASSOC);
                $timeEnd4 = dbtime();

                if ($this->isDebug)
                {
                    $stmt = $conn->prepare('explain '.$sql);
                    $stmt->execute($bind);
                    $explain = $stmt->fetchAll(PDO::FETCH_ASSOC);
                    BeanFinder::get('debug')->info(array('sql' => $echoSql, 'explain' => $explain));
                }

                $time = ($timeEnd4 - $timeStart) * 1000;
                $time2 = ($timeStart2- $timeStart) * 1000;
                $time3 = ($timeEnd2 - $timeStart2) * 1000;
                $time4 = ($timeEnd3 - $timeEnd2) * 1000;
                $time5 = ($timeEnd4 - $timeEnd3) * 1000;

                $this->writeDevLog($time, $echoSql);
                if ($time > 1000) {
                    error_log(date('m-d H:i:s')." - [$time] - [$time2] - [$time3] - [$time4] - [$time5] - [{$this->dataSource->getConnectionStatus4Read()}] - ".$echoSql."\n", 3, '/tmp/slowsqls.log');
                }
            }
            return $result;
        } catch (Exception $e) {
            error_log(print_r($e, true), 3, '/tmp/exception_db.log');
            throw new Exception("SqlError : {$this->buildSql($sql, $bind)}, Message : ".$e->getMessage().", Trace : ".$e->getTraceAsString());
        }
    }
    public function queryColumn($sql, $columnName, $bind = array()) {
        $result = array();
        $rows = $this->query($sql, $bind);
        foreach ($rows as $row) {
            $result[] = $row[strtolower($columnName)];
        }
        return $result;
    }

    public function queryValue($sql, $columnName, $bind = array()) {
        $rows = $this->queryColumn($sql, $columnName, $bind);
        if (isset($rows[0]))
            return $rows[0];
        return NULL;
    }

    public function beginTransaction() {
        $conn = $this->dataSource->connect4Write();
        $conn->setAttribute(PDO::ATTR_AUTOCOMMIT, false);
        $conn->beginTransaction();
    }

    public function commit() {
        $conn = $this->dataSource->connect4Write();
        $conn->commit();
        $conn->setAttribute(PDO::ATTR_AUTOCOMMIT, true);
    }

    public function rollBack() {
        $conn = $this->dataSource->connect4Write();
        $conn->rollBack();
        $conn->setAttribute(PDO::ATTR_AUTOCOMMIT, true);
    }
    public function buildSql($sql, $bind = array()) {
        if (empty($bind)) {
            return $sql;
        }
        $count = count($bind);
        $bindcount = 0;
        for ($i = 0; $i < $count; ++$i) {
            if (isset($bind[$i])) $bindcount++;
        }
        if ($bindcount == $count) {
            foreach ($bind as $value) {
                $sql = preg_replace('/\?/', "'$value'", $sql, 1);
            }
        } else {
            foreach ($bind as $key => $value) {
                $sql = str_replace($key, "'$value'", $sql);
            }
        }
        return $sql;
    }
    private function adjust(&$sql, &$bind) {
        $tmp = array();
        foreach ($bind as $key => $item) {
            if (is_array($item)) {
            $subBind = array();
                foreach ($item as $i => $v) {
                    $subBind[$key.$i.'p'] = $v;
                    $tmp[$key.$i.'p'] = $v;
                }
                $sql = str_replace($key, implode(',', array_keys($subBind)), $sql);
                unset($subBind);
            } else {
                $tmp[$key] = $item;
            }
        }
        $bind = $tmp;   
    }
    private function writeDevLog($time, $sql) {
        if ($this->isDebug)
        {
            error_log("[$time]".$sql."\n", 3, '/tmp/allsqls.log');
        }
    }
}
class SqlStatement {
    public $sql;
    public $bind;
    public function __construct($sql, $bind) {
        $this->sql = $sql;
        $this->bind = $bind;
    }
    public function execute($db) {
        return $db->execute($this->sql, $this->bind);
    }
    public function query($db) {
        return $db->query($this->sql, $this->bind);
    }
}

function dbtime()
{
    list($usec, $sec) = explode(" ", microtime());
    return ((float)$usec + (float)$sec);
}
?>
